/**
 * 
 */
package org.stars.daostars.core;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.stars.dao.exception.DaoException;
import org.stars.util.reflect.BeanUtil;
import org.stars.util.reflect.Getter;
import org.stars.util.reflect.Setter;

/**
 * Classe di utilità per i Dao. Viene principalmente dai metodi del DaoBase.
 * 
 * @author Francesco Benincasa (908099)
 * @author Alessandro Svetina (908303)
 * 
 */
public class Helper {

	/**
	 * Data una classe ed una DaoMap, si ottengono tutti i 
	 * PropertyDescriptorEx necessari
	 * @param clazz
	 * @param map
	 * @return
	 */
	public static PropertyDescriptorEx[] convert(Class<?> clazz, DaoMap map) {
		Map<String, Setter> mapReader=BeanUtil.getSetters(clazz); 
		
		PropertyDescriptorEx[] ret = new PropertyDescriptorEx[mapReader.size()];
		int i = 0;
		
		if (map!=null)
		{
			for (Setter item: mapReader.values())
			{
				ret[i] = new PropertyDescriptorEx(item, map.getTypeHandler(item.getPropertyName()));
				i++;
			}
		} else 
		{
			for (Setter item: mapReader.values()) {
				ret[i] = new PropertyDescriptorEx(item , null);
				i++;
			}
		}

		return ret;
	}
	
	/**
	 * Data una classe ed una DaoMap, si ottengono tutti i 
	 * PropertyDescriptorEx necessari
	 * @param clazz
	 * @param map
	 * @return
	 */
	public static PropertyDescriptorEx[] convertFromGetter(Class<?> clazz) {
		Map<String, Getter> mapGetter=BeanUtil.getGetters(clazz); 
		
		PropertyDescriptorEx[] ret = new PropertyDescriptorEx[mapGetter.size()];
		int i = 0;
		
			for (Getter item: mapGetter.values()) {
				ret[i] = new PropertyDescriptorEx(item);
				i++;
			}

		return ret;
	}


	/**
	 * Dato un parametro di tipo array, restituisce l'elenco dei suoi valori. Se
	 * e' un array, vengono restituiti tutti gli elementi dell'array, se è una
	 * stringa, allora la stringa viene trasformata in un array di elementi
	 * separati da virgola.
	 * <p>
	 * <b>NB: non viene effettuato alcun tipo di trim, quindi bisogna stare
	 * molto attenti agli spazi che sono presenti</b>.
	 * 
	 * @param parameter
	 * @param bean
	 * @param indexParameter
	 * @return array di valori
	 * @throws Exception
	 *             in caso di errore
	 */
	public static Object[] getArrayOfValue(SqlParameter parameter, Object bean, Object[] indexParameter) throws Exception {
		Object res = getValue(parameter, bean, indexParameter);
		if (res == null) {
			Object[] res1 = new Object[0];

			return res1;
		} else if (res.getClass().isArray()) {
			return (Object[]) res;
		} else if (res instanceof String) {
			String app = (String) res;
			String[] appA = app.split(",");
			return (Object[]) appA;
		} else {
			Object[] res1 = new Object[1];

			res1[0] = res;

			return res1;
		}
	}

	/**
	 * logger della classe
	 * 
	 * @return logger
	 */
	public static Log getLog() {
		return LogFactory.getLog(Helper.class);
	}
 
	/**
	 * Dato un DaoSqlParameter, recupera il relativo valore dal set degli
	 * attributi dell'entity bean e dei parametri indicizzati. Se il parametro è
	 * un attributo e l'entity bean non contiene tale attributo, viene sollevata
	 * un'eccezione. Se è un parametro di tipo index, viene restituito
	 * <code>null</code>.
	 * 
	 * @param parameter
	 * @param bean
	 * @param arrayIndexParameter
	 * @return Object contenente il valore del parametro
	 * @throws Exception
	 *             In caso di errore
	 */
	public static Object getValue(SqlParameter parameter, Object bean, Object[] arrayIndexParameter) throws Exception {
		
		Object temp; 
		if (parameter.getType() == SqlParameterType.TYPE_ATTRIBUTE) {
			temp=BeanUtil.getPropertyValue(bean, parameter.getAttribute());

		} else {
			// dobbiamo prendere il valore dall'array di parametri
			int index = parameter.getParameterIndex();

			if (arrayIndexParameter == null) {
				String msg = "It's impossible to get the parameter with index " + index + " because there's no array of indexParameter. The parameter get value NULL";
				Log log = getLog();
				log.error(msg);
				return null;
			}

			if (index >= arrayIndexParameter.length) {
				String msg = "It's impossible to get the parameter with index " + index + " because the indexedParameter has lenght " + arrayIndexParameter.length
						+ " and the max index is " + (arrayIndexParameter.length - 1) + ". The parameter get value NULL";
				Log log = getLog();
				log.error(msg);
				return null;
			}

			temp=arrayIndexParameter[index];
		}
		
		//FIX su enum
		if (temp!=null && temp.getClass().isEnum())
		{
			temp=temp.toString();
		} 
		
		return temp;
	}


	/**
	 * Invoca un getter di una proprietà
	 * 
	 * @param obj
	 * @param attributeName
	 * @param value
	 * @param map
	 * @return true se è riuscito ad invocarlo
	 */
	public static boolean invokeSetter(Object obj, String attributeName, Object value, Hashtable<String, Method> map) {

		try {
			String nomeMetodo = ("set" + attributeName).toLowerCase();
			if (map.containsKey(nomeMetodo)) {
				Method m = map.get(nomeMetodo);
				m.invoke(obj, value);
				return true;
			}
		} catch (Exception e) {

		}
		return false;
	}

	/**
	 * Invoca un getter di una proprietà
	 * 
	 * @param obj
	 * @param attributeName
	 * @param map
	 * @return true se è riuscito ad invocarlo
	 */
	public static Object invokeGetter(Object obj, String attributeName, Hashtable<String, Method> map) {

		try {
			String nomeMetodo = ("get" + attributeName).toLowerCase();
			if (map.containsKey(nomeMetodo)) {
				Method m = map.get(nomeMetodo);
				m.invoke(obj);
				return true;
			}
		} catch (Exception e) {

		}
		return false;
	}

	public static boolean existsSetter(String attributeName, Hashtable<String, Method> map) {

		try {
			String nomeMetodo = ("set" + attributeName).toLowerCase();
			if (map.containsKey(nomeMetodo)) {
				return true;
			}
		} catch (Exception e) {

		}
		return false;
	}

	/**
	 * Recupera tutti i metodi di una classe e delle sue classi ancestro
	 * 
	 * @param clazz
	 *            classe da analizzare.
	 * @return hashtable dei metodi, i cui nomi sono chiavi in lowercase
	 */
	@SuppressWarnings("unchecked")
	public static Hashtable<String, Method> getAllMethods(Class clazz) {
		Hashtable<String, Method> ret = new Hashtable<String, Method>();

		retrieveMethods(clazz, ret);

		return ret;
	}

	/**
	 * Per ogni classe recupera tutti i metodi e li mette nella hashtable
	 * passata come parametro.
	 * 
	 * @param clazz
	 * @param map
	 */
	private static void retrieveMethods(Class<?> clazz, Hashtable<String, Method> map) {
		if (clazz == null)
			return;
		Method[] m = clazz.getDeclaredMethods();

		retrieveMethods(clazz.getSuperclass(), map);

		for (Method item : m) {
			map.put(item.getName().toLowerCase(), item);
		}
	}

	/*
	 * Costruttore per oggetti generici. @param <T> il tipo di classe @param
	 * clazz la classe da istanziare. @return
	 */
	public static <T> T makeObject(Class<T> clazz) {
		try {

			/*
			 * for (Constructor c: el) System.out.println("The class of "
			 * +c.toString());
			 */
			return clazz.getConstructor().newInstance();
		} catch (Exception e) {
			Log log = getLog();
			log.error(e.toString());
			return null;
		}
	}
	
	public class PropertyTransfer
	{
		public Getter getter;
		public Setter setter;
	}

	/**
	 * Costruttore per i dao generici. La prima classe è la classe del Dao, la
	 * seconda quella degli entity entity bean che gestiscono
	 * 
	 * @param <T>
	 *            tipo di Dao
	 * @param <A>
	 *            tipo di Entity Bean
	 * @param clazz
	 *            la classe del dao
	 * @param class2
	 *            la classe dell'entity bean
	 * @return istanza dell'oggetto da istanziare
	 */
	@SuppressWarnings("unchecked")
	public static <T, A> T makeObject(Class<T> clazz, Class<A> class2) {
		try {
			Constructor[] el = clazz.getConstructors();
			return (T) el[0].newInstance(class2);
		} catch (Exception e) {
			Log log = getLog();
			log.error(e.toString());
			return null;
		}
	}

	/**
	 * Returns a PropertyDescriptor[] for the given Class.
	 * 
	 * @param <T>
	 * 
	 * @param c
	 *            The Class to retrieve PropertyDescriptors for.
	 * @return A PropertyDescriptor[] describing the Class.
	 * @throws DaoException
	 *             if introspection failed.
	 */
	public static <T> MethodDescriptor[] methodDescriptors(Class<T> c) throws DaoException {
		BeanInfo beanInfo;
		try {
			beanInfo = Introspector.getBeanInfo(c);

		} catch (IntrospectionException e) {
			DaoException ex = new DaoException("Bean introspection failed: " + e.getMessage());
			throw ex;
		}

		return beanInfo.getMethodDescriptors();
	}

}
